X_NO3RDPARTY_VERSION = 1;
no3rdPartyExtensionDir = __LOCATION__.parent.parent;

// Yikes. This doesn't work because Mozilla is annoying.  Work-arounds require some
// research. 
//Components.utils.import("resource://no3rdparty-modules/no3rdPartyUtil.jsm");


//
// Component Class to observe HTTP GET requests and act on them.  
// Calls no3rdPartyReporter to do the actual reporting/logging
// of data.
//
no3rdPartyUtil = {
	eTLDService : Components.classes["@mozilla.org/network/effective-tld-service;1"].getService(Components.interfaces.nsIEffectiveTLDService),
	getTLD : function (hostname) {
		//var parts = hostname.split('.');
		//var slice = (parts[parts.length-2].length==2)?parts.length-3: parts.length-2;
		//return parts.slice(slice).join('.');
		return this.eTLDService.getBaseDomainFromHost(hostname); 
	},

	matchDomains : function(originatingPageHost, channelDomain) {
		var origTLD = this.getTLD(originatingPageHost);
		var channelTLD = this.getTLD(channelDomain);
		return (origTLD == channelTLD) ? true : false;
 	},
}; //no3rdPartyUtil

// Logger
var no3rdPartyLogger = { 	
	debug: function(message) {
   		this.writeFile("debug.log", message + "\n");
  	},

	log: function(message) {
   		this.debug(message);
		this.writeFile("no3rdParty.log", message + "\n");
  	},

	writeFile : function (filename,message) {
		var outFile = no3rdPartyExtensionDir.clone();
		outFile.append(filename)
		if(outFile.exists() == false) {
 			outFile.create( Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 420);
 		}
		//create file output stream and use write/create/truncate mode
		//0x02 writing, 0x08 create file, 0x20 truncate, length if exist, 0x10 append
		var stream = Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream);
		stream.init(outFile, 0x02 | 0x08 | 0x10, 0666, 0);

		//write data to file then close output stream
			
		stream.write(message, message.length);
		stream.close();

	}
}; //no3rdParty Logger


function No3rdPartyPreferences() {
	this.init();
};

No3rdPartyPreferences.prototype = {
	mozPrefs: null,
	
	// Options
	_server: null,
	_shouldRun: null,
	_shouldUpload: null,
	_googleUser: null,
	_uniqueID: null,
	_reportDeleteCancel: null,
	_storeLocally: null,
	_maxLocalStorage: null,
	_highlight3rdPartyResources: null,
	_displayStatusCount: null,

			
	init: function() {
		no3rdPartyLogger.debug("Initializing Preferences");
		// Register to receive notifications of preference changes
		this.mozPrefs = Components.classes["@mozilla.org/preferences-service;1"]
				.getService(Components.interfaces.nsIPrefService)
				.getBranch("extensions.no3rdparty.");
		this.mozPrefs.QueryInterface(Components.interfaces.nsIPrefBranch2);
		this.mozPrefs.addObserver("", this, false);
	
		// init values

		this._server = this.mozPrefs.getCharPref("server");
		this._shouldRun = this.mozPrefs.getBoolPref("shouldRun");
		this._shouldUpload = this.mozPrefs.getBoolPref("shouldUpload");
		this._googleUser = this.mozPrefs.getCharPref("googleUsername");
		this._uniqueID = this.mozPrefs.getCharPref("uniqueID");
		if (! this._uniqueID ) {
			this.uniqueID = this.generateUniqueID();
		}
		this._reportDeleteCancel = this.mozPrefs.getCharPref("reportDeleteCancel");
		this._storeLocally = this.mozPrefs.getBoolPref("storeLocally");
		this._maxLocalStorage = this.mozPrefs.getIntPref("maxLocalStorage");

		no3rdPartyLogger.debug(this.toString());
	},
	
	shutdown: function() {
		this.mozPrefs.removeObserver("", this);
	},
	
	observe: function(subject, topic, data) {
		if (topic != "nsPref:changed") {
			return;
		}

		switch(data) {
			case "server":
				this._server = this.mozPrefs.getCharPref("server");
				break;
				
			case "shouldRun":
				this._shouldRun = this.mozPrefs.getBoolPref("shouldRun");
				break;
				
			case "shouldUpload":
				this._shouldUpload = this.mozPrefs.getBoolPref("shouldUpload");
				break;
				
			case "googleUsername":
				this._ColorRequests = this.mozPrefs.getCharPref("googleUsername");
				break;
				
			case "uniqueID":
				this._uniqueID = this.mozPrefs.getCharPref("uniqueID");
				if (! this._uniqueID) {
					this._uniqueID = this.generateUniqueID();
				}
				break;
				
			case "reportDeleteCancel":
				this._reportDeleteCancel = this.mozPrefs.getCharPref("reportDeleteCancel");
				break;

			case "storeLocally":
				this._storeLocally = this.mozPrefs.getBoolPref("storeLocally");
				break;

			case "maxLocalStorage":
				this._maxLocalStorage = this.mozPrefs.getIntPref("maxLocalStorage");
				break;
			
			case "displayStatusCount":
				this._displayStatusCount = this.mozPrefs.getIntPref("displayStatusCount");
				break;
			case "highlight3rdPartyResources":
				this._highlight3rdPartyResources = this.mozPrefs.getIntPref("highlight3rdPartyResources");
				break;
		}
	},
	
	get server() { 
		return this._server;
	},
	set server(value) {
		this._server = value;
		this.mozPrefs.setCharPref("server", value);
	},

	get shouldRun() { 
		return this._shouldRun;
	},
	set shouldRun(value) {
		this._shouldRun = value;
		this.mozPrefs.setBoolPref("shouldRun", value);
	},

	get shouldUpload() { 
		return this._shouldUpload;
	},
	set shouldUpload(value) {
		this._shouldUpload = value;
		this.mozPrefs.setBoolPref("shouldUpload", value);
	},

	get googleUsername() { 
		return this._googleUsername;
	},
	set googleUsername(value) {
		this._googleUsername = value;
		this.mozPrefs.setCharPref("googleUsername", value);
	},

	get uniqueID() { 
		return this._uniqueID;
	},
	set uniqueID(value) {
		this._uniqueID = value;
		this.mozPrefs.setCharPref("uniqueID", value);
	},

	get reportDeleteCancel() { 
		return this._reportDeleteCancel;
	},
	set reportDeleteCancel(value) {
		this._reportDeleteCancel = value;
		this.mozPrefs.setCharPref("reportDeleteCancel", value);
	},

	get storeLocally() { 
		return this._storeLocally;
	},
	set storeLocally(value) {
		this._storeLocally = value;
		this.mozPrefs.setBoolPref("storeLocally", value);
	},

	get maxLocalStorage() { 
		return this._maxLocalStorage;
	},
	set maxLocalStorage(value) {
		this._maxLocalStorage = value;
		this.mozPrefs.setIntPref("maxLocalStorage", value);
	},


	get displayStatusCount() { 
		return this._displayStatusCount;
	},
	set displayStatusCount(value) {
		this._displayStatusCount = value;
		this.mozPrefs.setIntPref("displayStatusCount", value);
	},

	get highlight3rdPartyResources() { 
		return this._highlight3rdPartyResources;
	},
	set highlight3rdPartyResources(value) {
		this._highlight3rdPartyResources = value;
		this.mozPrefs.setIntPref("highlight3rdPartyResources", value);
	},


	generateUniqueID : function(){
		function S4() {
			return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
		}
		return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4());
	},
	
};


var no3rdPartyRequestObserver = {
	initiated : false,
  	date : new Date(),
	prefs : new No3rdPartyPreferences(),

	// Get outgoing http headers for GET requests.
	observe: function(subject, topic, data) {
		if (!this.prefs.shouldRun) {
			return;
		}
   		if (topic == "http-on-modify-request" && this.prefs.shouldRun) {
			this.onModifyRequest(subject,data);
	
		}
		else if (topic == "profile-after-change") {
			this.profileAfterChange();
		}
		else if (topic == "browser-lastwindow-close-requested" || topic =="dom-window-destroyed" 
						|| topic =="idle" || topic =="xul-window-destroyed"
						|| topic =="quit-application-requested") {
			no3rdPartyLogger.debug("Other Topic: " + topic);
			no3rdPartyReporter.flush();
		}
  	},

  	observerService: function() {
    	return Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
  	},
	
	idleObserverService: function() {
		return Components.classes["@mozilla.org/widget/idleservice;1"].getService(Components.interfaces.nsIIdleService);
	},
  	
	register: function() {
		if (! this.initiated) {
	    	this.observerService().addObserver(this, "http-on-modify-request", false);
	    	this.observerService().addObserver(this, "browser-lastwindow-close-requested", false);
	    	this.observerService().addObserver(this, "dom-window-destroyed", false);
	    	this.observerService().addObserver(this, "xul-window-destroyed", false);
	    	this.observerService().addObserver(this, "quit-application-requested", false);
			this.idleObserverService().addIdleObserver(this,10);

			this.prefs.init();

			this.initiated = true;
			return this;
		}
		return this;
	},

  	unregister: function() {
	    	this.observerService().removeObserver(this, "http-on-modify-request", false);
	    	this.observerService().removeObserver(this, "browser-lastwindow-close-requested", false);
	    	this.observerService().removeObserver(this, "dom-window-destroyed", false);
	    	this.observerService().removeObserver(this, "xul-window-destroyed", false);
	    	this.observerService().removeObserver(this, "quit-application-requested", false);
			this.idleObserverService().RemoveIdleObserver(this,10);
  	},
	
	QueryInterface: function(iid) {
		if (iid.equals(Components.interfaces.nsIObserver) || iid.equals(Components.interfaces.nsISupports)) {
			return this;
        }
		Components.returnCode = Components.results.NS_ERROR_NO_INTERFACE;
		return null;
	},

	
	onModifyRequest: function(subject,topic,data) {
		var httpChannel = subject.QueryInterface(Components.interfaces.nsIHttpChannel);
		var originatingPage = this.getLocationOfOriginatingWindow(httpChannel);
		if (!originatingPage) {
			no3rdPartyLogger.debug("No Originating Page for Request: " + httpChannel.URI.spec)
			return;
		}	
			
		var channelHost = httpChannel.URI.host;

 		if (!this.matchDomains(originatingPage.hostname, channelHost)) {
			// Gather the header information before otherwise modifying headers
			headers = new no3rdPartyHttpHeaderVisitor();
			httpChannel.visitRequestHeaders(headers);
			headers.channelURL = httpChannel.URI.spec;
			headers.channelTLD = no3rdPartyUtil.getTLD(httpChannel.URI.host);
			headers.origPageURL = originatingPage.href;
			headers.origPageTLD = no3rdPartyUtil.getTLD(originatingPage.hostname);
			headers.timeStamp = this.date.getTime();
			no3rdPartyReporter.add(headers);

			no3rdPartyLogger.debug("Action: " + this.prefs.reportDeleteCancel);
			if (this.prefs.reportDeleteCancel == "delete") {
				this.deleteRequestCookie(httpChannel);
			}
			else if (this.prefs.reportDeleteCancel == "cancel") {
				this.cancelRequest(httpChannel);
			}
			

		}
		return;
	},
	
	deleteRequestCookie: function(httpChannel) {
		httpChannel.setRequestHeader("Cookie","",false);
	},
	
	cancelRequest: function(httpChannel) {
		no3rdPartyLogger.debug("Canceling httpChannel: " + httpChannel.URI.spec);
		req = httpChannel.QueryInterface(Components.interfaces.nsIRequest);
		req.cancel(Components.results.NS_OK);
	},

	profileAfterChange: function() {
		this.register();
		return;
	},

	getLocationOfOriginatingWindow: function (httpChannel) {
		try {  
			var notificationCallbacks;

			if (httpChannel.notificationCallbacks) {
				notificationCallbacks = httpChannel.notificationCallbacks;
			}
			else if (httpChannel.loadGroup && httpChannel.loadGroup.notificationCallbacks) {
				notificationCallbacks = httpChannel.loadGroup.notificationCallbacks;		
			}
			else {
				return null;
			}
 
			return notificationCallbacks.getInterface(Components.interfaces.nsIDOMWindow).top.location;  
		}  
		catch (e) {
			no3rdPartyLogger.debug("Exception getting Window Location: " + e + "\nChannel URI: " + httpChannel.URI.spec); 
			return null;  
		}  
	},
	
	getTLD : function (hostname) {
		var parts = hostname.split('.');
		var slice = (parts[parts.length-2].length==2)?parts.length-3: parts.length-2;
		return parts.slice(slice).join('.')
	},

	matchDomains : function(originatingPageHost, channelDomain) {
		var origTLD = this.getTLD(originatingPageHost);
		var channelTLD = this.getTLD(channelDomain);
		return (origTLD == channelTLD) ? true : false;
 	},

};

//
// Used to record header data for logging when we inspect
// an outgoing HTTP GET call
//
no3rdPartyHttpHeaderVisitor = function ( ) {
	this._headers = new Object();
	this.origPageURL = "";
	this.channelURL = "";
	this.timeStamp = "";
	this.origPageTLD = "";
	this.channelTLD = "";
};

no3rdPartyHttpHeaderVisitor.prototype = {
	visitHeader : function ( header, value ) {
		this._headers[header] = value;
	},
  
	getHeaders : function ( ) {  
   		return this._headers;  
	},

	toString : function () {
		var headersString = "origPageURL: " + this.origPageURL + "\nchannelURL: " + this.channelURL + "\ntimeStamp: " + this.timeStamp + "\n";
		for (var header in this._headers) {
			headersString = headersString + header + ": " + this._headers[header] + "\n";
		}
		return headersString;
	},

	toJSON: function() {
		thisObject = {};
		thisObject.origPageURL = this.origPageURL;
		thisObject.channelURL = this.channelURL;
		thisObject.timeStamp = this.timeStamp;
		thisObject.headers = this._headers;
		thisObject.origPageTLD = this.origPageTLD;
		thisObject.channelTLD = this.channelTLD;
		return thisObject;
	}
};  

var no3rdPartyReporter = {
	requestList : new Array(), 
	prefs : new No3rdPartyPreferences(),
	
	add: function(thirdPartyRequest) {
		this.requestList.push(thirdPartyRequest)
		if (this.requestList.length > 10) {
			this.report()
		}
	},

	toString: function() {
		return this.requestList.join("\n");
	},
	
	formatForUpload : function () {
		return (JSON.stringify(this.requestList));	
	},
	
	formatForFile : function () {
		return this.toString();	
	},

	report: function() {
		if (this.requestList.length > 0) {
			if (this.prefs.shouldUpload && this.prefs.server) {
				no3rdPartyServer.send(this.formatForUpload());
			}
			if (this.prefs.storeLocally) {
				no3rdPartyFile.write(this.formatForFile());
			}
			this.requestList = [];
		}
	},

	flush: function() {
		this.report();
	}

};

var no3rdPartyServer = {
	prefs : new No3rdPartyPreferences(),
	submitCGI : "submit",

	send : function (data) {
		no3rdPartyLogger.debug("no3rdPartyServer.prefs\n" + this.prefs.toString());
		no3rdPartyLogger.debug("no3rdPartyServer data:\n" + data);
		var postRequest = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Components.interfaces.nsIXMLHttpRequest);
		postRequest.onreadystatechange = function(){
			if (postRequest.readyState == 4) {
					no3rdPartyLogger.debug("Response: " + postRequest.responseText);
				}
			}
		postRequest.open("POST",this.prefs.server + "/" + this.submitCGI,true);
		postRequest.setRequestHeader("Content-type", "text/plain");
		postRequest.setRequestHeader("Content-length", data.length);
		postRequest.setRequestHeader("X-No3rdParty-ID", this.prefs.uniqueID);
		postRequest.setRequestHeader("X-No3rdParty-Version", X_NO3RDPARTY_VERSION);
		postRequest.setRequestHeader("Connection", "close");
		no3rdPartyLogger.log(data);
		postRequest.send(data);
	},

};

var no3rdPartyFile = {
	fileName : "no3rdParty.log",
	prefs : new No3rdPartyPreferences(),

	write: function(data) {
		var outFile = no3rdPartyExtensionDir.clone();
		outFile.append(this.fileName)
		if(outFile.exists() == false)
 		{
 			outFile.create( Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 420);
 		}
		//if (outFile.filesize > (prefs.maxLocalStorage/2)) {
		//	outFile.copy()
		//	outFile.filesize = 0;
		//}
		//create file output stream and use write/create/truncate mode
		//0x02 writing, 0x08 create file, 0x20 truncate, length if exist, 0x10 append
		var stream = Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream);
		stream.init(outFile, 0x02 | 0x08 | 0x10, 0666, 0);

		//write data to file then close output stream
			
		stream.write(data, data.length);
		stream.close();
	}
};

var no3rdPartyModule = {
	compID: Components.ID("{E14BF596-F840-4F31-B0C3-8AE9770B8DA9}"),
    contractID: "@anthony.bak/no3rdParty;1",
    name:   "Filters 3rd Party Cookies and Content",

	registerSelf: function (compMgr, fileSpec, location, type) {
		var compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
		compMgr.registerFactoryLocation(this.compID, this.name,"service," + this.contractID,fileSpec,location,type);
        
		var catMgr = Components.classes["@mozilla.org/categorymanager;1"].getService(Components.interfaces.nsICategoryManager);
        catMgr.addCategoryEntry("profile-after-change", this.name, "service," + this.contractID, true, true);	
    	//no3rdPartyPreferences.init();
	},

	unregisterSelf: function(aCompMgr, aLocation, aType) {
		aCompMgr = aCompMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
		aCompMgr.unregisterFactoryLocation(this.compID, aLocation);       
	},

	getClassObject: function (compMgr, cid, iid) {
		return this.myFactory;
    },
	
	getService: function (compMgr, cid, iid) {
		return no3rdPartyRequestObserver;
    },

    myFactory: {
        QueryInterface: function (aIID) {
            if (!aIID.equals(Components.interfaces.nsISupports) && !aIID.equals(Components.interfaces.nsIFactory)) {
                throw Components.results.NS_ERROR_NO_INTERFACE;
			}
            return this;
		},
	
		createInstance: function (outer, iid) {
			return no3rdPartyRequestObserver;
		}
    },

    canUnload: function(compMgr) {
        return true;
    }
}


function NSGetModule(compMgr, fileSpec) {
    return no3rdPartyModule;
}
